package pt.mauromad.guias.controller;

import static java.lang.String.format;
import static pt.mauromad.guias.utils.CastUtils.cast;

import java.beans.PropertyEditorSupport;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;

import javax.servlet.http.HttpSession;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.context.request.WebRequest;

import pt.mauromad.guias.dao.AccountDao;
import pt.mauromad.guias.dao.AddressDao;
import pt.mauromad.guias.dao.CompanyDao;
import pt.mauromad.guias.dao.DomainDao;
import pt.mauromad.guias.dao.ProductDao;
import pt.mauromad.guias.dao.UserDao;
import pt.mauromad.guias.dao.manifest.ManifestDao;
import pt.mauromad.guias.dao.manifest.ManifestItemDao;
import pt.mauromad.guias.dao.security.RoleDao;
import pt.mauromad.guias.refdata.DocumentType;
import pt.mauromad.guias.refdata.mesure.Measure;
import pt.mauromad.guias.security.UserPrincipal;
import pt.mauromad.guias.service.AccountService;
import pt.mauromad.guias.service.AddressService;
import pt.mauromad.guias.service.CompanyService;
import pt.mauromad.guias.service.DomainService;
import pt.mauromad.guias.service.ManifestItemService;
import pt.mauromad.guias.service.ManifestService;
import pt.mauromad.guias.service.ProductService;
import pt.mauromad.guias.service.RoleService;
import pt.mauromad.guias.service.UserService;
import pt.mauromad.guias.utils.DateUtils;
import pt.mauromad.guias.utils.form.Action;

@Controller
public class BaseController {

	@Autowired
	protected DomainService domainService;

	@Autowired
	protected AccountService accountService;

	@Autowired
	protected UserService userService;

	@Autowired
	protected ManifestService manifestService;

	@Autowired
	protected ManifestItemService manifestItemService;

	@Autowired
	protected CompanyService companyService;

	@Autowired
	protected AddressService addressService;

	@Autowired
	protected ProductService productService;

	@Autowired
	protected RoleService roleService;

	@ModelAttribute
	public void ajaxAttribute(WebRequest request, Model model, HttpSession session) {
		if ("true".equals(request.getParameter("ajax")))
			model.addAttribute("isAjax", true);
	}

	@ModelAttribute
	public void genericAttributes(WebRequest request, Model model, HttpSession session) {
		try {
			model.addAttribute("principal", new UserPrincipal());
		} catch (Exception e) {
			// TODO
		}
	}

	@InitBinder
	public void dateBinder(WebDataBinder binder) {
		binder.registerCustomEditor(Date.class, new PropertyEditorSupport() {
			@Override
			public void setAsText(String text) {
				try {
					if (text == null || text.trim().length() == 0) {
						this.setValue(null);
					} else {
						this.setValue(DateUtils.fromString(text));
					}
				} catch (Exception e) {
					throw new IllegalArgumentException("could not parse date:" + text);
				}
			}
		});
	}

	@InitBinder
	public void documentTypeBinder(WebDataBinder binder) {
		binder.registerCustomEditor(DocumentType.class, new PropertyEditorSupport() {
			@Override
			public void setAsText(String text) {
				if (text == null || text.trim().length() == 0) {
					this.setValue(null);
				} else {
					this.setValue(DocumentType.byId(Integer.parseInt(text)));
				}
			}
		});
	}

	@InitBinder
	public void manifestTypeBinder(WebDataBinder binder) {
		binder.registerCustomEditor(ManifestDao.class, new PropertyEditorSupport() {
			@Override
			public void setAsText(String text) {
				if (text == null || text.trim().length() == 0) {
					this.setValue(null);
				} else {
					this.setValue(manifestService.get(Long.parseLong(text)));
				}
			}
		});
	}

	@InitBinder
	public void manifestItemTypeBinder(WebDataBinder binder) {
		binder.registerCustomEditor(ManifestItemDao.class, new PropertyEditorSupport() {
			@Override
			public void setAsText(String text) {
				if (text == null || text.trim().length() == 0) {
					this.setValue(null);
				} else {
					this.setValue(manifestItemService.get(Long.parseLong(text)));
				}
			}
		});
	}

	@InitBinder
	public void userTypeBinder(WebDataBinder binder) {
		binder.registerCustomEditor(UserDao.class, new PropertyEditorSupport() {
			@Override
			public void setAsText(String text) {
				if (text == null || text.trim().length() == 0) {
					this.setValue(null);
				} else {
					this.setValue(userService.get(getDomain(), text));
				}
			}
		});
	}

	@InitBinder
	public void accountTypeBinder(WebDataBinder binder) {
		binder.registerCustomEditor(AccountDao.class, new PropertyEditorSupport() {
			@Override
			public void setAsText(String text) {
				if (text == null || text.trim().length() == 0) {
					this.setValue(null);
				} else {
					this.setValue(domainService.get(text).getAccount());
				}
			}
		});
	}

	@InitBinder
	public void companyTypeBinder(WebDataBinder binder) {
		binder.registerCustomEditor(CompanyDao.class, new PropertyEditorSupport() {
			@Override
			public void setAsText(String text) {
				if (text == null || text.trim().length() == 0) {
					this.setValue(null);
				} else {
					this.setValue(companyService.get(Long.parseLong(text)));
				}
			}
		});
	}

	@InitBinder
	public void addressTypeBinder(WebDataBinder binder) {
		binder.registerCustomEditor(AddressDao.class, new PropertyEditorSupport() {
			@Override
			public void setAsText(String text) {
				if (text == null || text.trim().length() == 0) {
					this.setValue(null);
				} else {
					this.setValue(addressService.get(Long.parseLong(text)));
				}
			}
		});
	}

	@InitBinder
	public void productTypeBinder(WebDataBinder binder) {
		binder.registerCustomEditor(ProductDao.class, new PropertyEditorSupport() {
			@Override
			public void setAsText(String text) {
				if (text == null || text.trim().length() == 0) {
					this.setValue(null);
				} else {
					this.setValue(productService.get(Long.parseLong(text)));
				}
			}
		});
	}

	@InitBinder
	public void domainTypeBinder(WebDataBinder binder) {
		binder.registerCustomEditor(DomainDao.class, new PropertyEditorSupport() {
			@Override
			public void setAsText(String text) {
				if (text == null || text.trim().length() == 0) {
					this.setValue(null);
				} else {
					DomainDao domain = domainService.get(text);
					if (!isAdministrator() && !getDomain().equals(domain)) {
						throw new IllegalArgumentException("illegal domain access");
					}
					this.setValue(domainService.get(text));
				}
			}
		});
	}

	@InitBinder
	public void measureTypeBinder(WebDataBinder binder) {
		binder.registerCustomEditor(Measure.class, new PropertyEditorSupport() {
			@Override
			public void setAsText(String text) {
				if (text == null || text.trim().length() == 0) {
					this.setValue(null);
				} else {
					this.setValue(Measure.byId(Long.parseLong(text)));
				}
			}
		});
	}

	@InitBinder
	public void roleTypeBinder(WebDataBinder binder) {
		binder.registerCustomEditor(RoleDao.class, new PropertyEditorSupport() {
			@Override
			public void setAsText(String text) {
				if (text == null || text.trim().length() == 0) {
					this.setValue(null);
				} else {
					this.setValue(roleService.get(text));
				}
			}
		});
	}

	@Autowired
	protected HttpSession session;

	public UserDao getUser() {
		return userService.get(getDomain(), getUsername());
	}

	public AccountDao getAccount() {
		return getDomain().getAccount();
	}

	public DomainDao getDomain() {
		return domainService.get(new UserPrincipal().getDomain());
	}

	public String getUsername() {
		return new UserPrincipal().getUsername();
	}

	protected boolean isAdministrator() {
		return new UserPrincipal().isAdministrator();
	}

	protected boolean applies(RoleDao... roles) {
		UserDao currentUser = getUser();
		for (RoleDao role : roles) {
			if (currentUser.hasRole(role))
				return true;
		}
		return false;
	}

	protected void add(Model model, Action action) {
		Set<Action> actions = cast(model.asMap().get("actions"));

		if (actions == null) {
			actions = new HashSet<>();
			model.addAttribute("actions", actions);
		}

		actions.add(action);
	}

	protected void setEditable(Model model) {
		model.addAttribute("editable", true);
	}

	protected String redirect(String path, Object... objects) {
		return buildTargetUrl("redirect:%s", path, objects);
	}

	protected String forward(String path, Object... objects) {
		return buildTargetUrl("forward:%s", path, objects);
	}

	private String buildTargetUrl(String target, String path, Object... objects) {
		target = format(target, path);

		if (objects == null)
			return target;

		return format(target, objects);
	}
}
